#! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh 'perf.c' <<'END_OF_FILE' X X/* X * P e r f e c t H a s h X * X * X * Program to search for Minimal Perfect Hash Functions for X * use in lexical analyzers. C.D. Havener Jan 26 1982 X * GenRad Inc. 37 Great Road, Bolton Mass. 01740 X * Based on paper "Minimal Perfect Hash Functions Made Simple" X * by Richard J. Cichelli - Comm. of ACM Jan 1980 pp 17-19 X * X * Synopsis: The hash function is h = assoc value of 1st X * letter + length of keyword + assoc value of last letter X * X * This program finds the associated values of the letters X * given a list of keywords, 1 per line. It works most of X * the time for up to about 40 keywords but certain X * pathological cases exist. A semi-perfect hash is usually X * found by the program. The user can then tighten the X * default limits for max associated char value or the X * table limit using the -v and -t options. Sometimes the X * presort heuristics actually make the search process X * much more difficult. The user can try his luck at manual X * sorting using the -n option. Since the hash function X * produces such a limited range of numbers it can only work X * for up to about 40 keywords. If a language needs say 80 X * keywords just split them up into two tables and let the X * lexical analyzer look in first one then the other, this X * will still be much faster than any other keyword lookup. X * X * This program has run sucessfully on a VAX 11/780 under X * 4.1BSD and VMS (using Vax-11 C and Decus C). X */ X X/*)BUILD X $(MP) = 1 # uses macros with arguments X $(STACK) = 10000 # lots of recursion X $(TKBOPTIONS) = { X STACK = 1024 X TASK = ...PRF X } X*/ X X#ifdef DOCUMENTATION Xtitle perfect Find Perfect Hash Functions Xindex perfect Find perfect hash functions X Xsynopsis X X perfect [-options] X Xdescription X X perfect reads a list of keywords from the standard input X and computes a "perfect hash" function for the set. X The following options are defined: X .lm +8 X .s.i -8;-d######Enable debug code. X .s.i -8;-n######Disable pre-sort of keywords. (See below). X .s.i -8;-t###Limit the maximum table size to entries. X .s.i -8;-v###Limit the maximum associated character value. X .s.i -8;-k###Use only the first keywords in the list. X .s.i -8;-a###Give a status report every seconds. X (Unix and Vax-11C/VMS only). X .s.i -8;-o#file#Write a sample keyword lookup routine to the X indicated file. X .s.lm -8 X The program prints a running commentary on the standard X output. X XAuthor X X Charlie Havener, GenRad Inc. Bolton Mass. X X (Modified by Martin Minow, Dec, Maynard Mass.) X XDiscussion X X This technical note describes an implementation of a X pragmatic algorithm for finding perfect or semi-perfect hash X functions for lists of keywords. The resulting hash function can X be used to speed up lexical analyzers used in translators and X compilers. The algorithm was described by R.J. Cichelli in The X January 1980 issue of Communications of the ACM under the title X "Minimal Perfect Hash Functions made Simple." The article did not X include a computer language description of the algorithm and some X important implementation details were unclear. It is assumed that X the user will know what to do with the output of this program. X The -o option may be used to produce a lexical analyser kernel X from the tables built by this program. Another reference for X those wishing to pursue the topic further is "More On Minimal X Perfect Hash tables" by Curtis Cook and R. Oldehoeft, a Colorado X State University Technical Report. X X The program takes a list of keywords and sorts them in such X a way that the search time for a hash function will be X reasonable. Once sorted a recursive trial and error procedure X hunts for a hash function satisfying user supplied bounds X of table size and associated character value limits. The X built-in hash function is X X hash = assoc value of first character X + keyword length X + assoc value of last character X X It is critically important to select a good ordering of the X keywords before searching begins. I ran up 100 hours of VAX time X searching for a hash function with an unordered list, and gave X up. Once the sort heuristics were debugged it found the hash X function in minutes. Typically it will find the function in X minutes or you may as well give up. A status reporting feature is X built into the program on the UNIX system that lets you follow X the progress of the search depth. If it has trouble, you can X tell just which word it can't get past, and take appropriate X action. If it has trouble you can attempt to alter its choice of X pre-ordering by moving troublesome words to the front of the X list. In a sequel to his paper, Cichelli stated that sometimes X the sort heuristics makes the search longer. There is also no X guarantee that a hash function can be found. The program does the X obvious precheck that no two keywords have the same first and X last letter and length in common (e.g. BAK KAB). Nonetheless, X as pointed out by Jaeschke and Osterberg in an overly harsh X criticism, there are many pathological sets of keywords that fail. X (On Cichelli's minimal perfect hash functions method. Comm. ACM X Dec. 1980, pp 728-729) The algorithm also only works for up to X about 40 keywords due to the limited range of numbers the formula X can generate. If you have, say 80 keywords, just make two hash X tables and look in each one. Here are some examples of the X program's output: X .s.nf X Perfect hash function finder, CDH Ver. 2.9 X Start time = Mon Oct 17 20:40:40 1983 X 28 keywords, 19 distinct letters. X The associated char value limit is 19 X The table size limit is 100 X The search ordering is X else double continue case float struct sizeof X static short extern typedef default register for X char while entry int if return do unsigned switch X union goto auto long break X .s X Success! Associated Char Values Follow: X a =19, b = 4, c = 1, d = 0, e = 0, f = 3, g =17, X h =14, i =17, k =19, l = 6, n = 8, o = 0, r =11, X s = 6, t = 0, u =16, w =13, y =14, X Hash min = 2, max = 30, spread = 29 X do 2, else 4, case 5, double 6, default 7, X float 8, continue 9, typedef 10, short 11, struct 12, X static 13, extern 14, sizeof 15, char 16, for 17, X while 18, entry 19, int 20, goto 21, if 22, auto 23, X unsigned 24, return 25, switch 26, long 27, break 28, X union 29, register 30, X .s X Total search() invocations = 15913 X Started Mon Oct 17 20:40:40 1983 X Finished Mon Oct 17 20:40:42 1983 X .s.nf X Perfect hash function finder, CDH Ver. 2.9 X Start time = Mon Oct 17 20:41:11 1983 X 39 keywords, 19 distinct letters. X The associated char value limit is 19 X The table size limit is 100 X The search ordering is X TEXT RESET TRUE REWRITE READLN SQRT SQR EOLN TRUNC X PUT EXP PAGE CHR CHAR COS SUCC READ ROUND DISPOSE X PRED SIN OUTPUT ORD INPUT INTEGER GET MAXINT REAL X WRITE EOF FALSE NEW WRITELN LN ARCTAN ABS BOOLEAN X PACK UNPACK X .s X Success! Associated Char Values Follow: X A =18, B =11, C =16, D =18, E = 3, F = 3, G = 0, X I = 1, K =19, L = 9, M =18, N =19, O = 8, P = 9, X R = 0, S =14, T = 0, U =13, W =19, X Hash min = 3, max = 45, spread = 43 X GET 3, TEXT 4, RESET 5, INPUT 6, TRUE 7, X INTEGER 8, EOF 9, REWRITE 10, FALSE 11, PUT 12, X REAL 13, OUTPUT 14, EXP 15, PAGE 16, SQR 17, SQRT 18, X CHR 19, CHAR 20, TRUNC 21, READ 22, ROUND 23, X MAXINT 24, READLN 25, EOLN 26, WRITE 27, DISPOSE 28, X ORD 29, LN 30, PRED 31, PACK 32, COS 33, SUCC 34, X ABS 35, SIN 36, BOOLEAN 37, UNPACK 38, NEW 41, X ARCTAN 43, WRITELN 45, X .s X Total search() invocations = 149292 X Started Mon Oct 17 20:41:11 1983 X Finished Mon Oct 17 20:41:35 1983 X .s 2.f X Usually, the first time you run perf, just let everything X default. The second time, X use the -t option to limit the table size to the first hash X value plus the number of keywords. X X On Unix and Vax/VMS (Vax-11 C), The program will accept X SIGTERM signals (CTRL/C on VMS) for an update status report X since it may take quite a while to find the hash function values. X XSample keyword tables X X The following tables are known to work correctly. X The first defines the keywords for the C programming X language; the second for a toy computer language. X X int char float double struct union long short X unsigned auto extern register typedef static X goto return sizeof break continue if else for X do while switch case default entry X X GET TEXT RESET OUTPUT MAXINT INPUT TRUE X INTEGER EOF REWRITE FALSE CHR CHAR TRUNC REAL X SQR SQRT WRITE PUT ORD READ ROUND READLN EXP X PAGE EOLN COS SUCC DISPOSE NEW ABS LN BOOLEAN X WRITELN SIN PACK UNPACK ARCTAN PRED X X#endif X X#include X#include X X#define EOS '\0' X#define FALSE 0 X#define TRUE 1 X#define VOID int X X#ifdef unix X#define UNIX 1 X#define DOALARM 1 X#endif X X#ifndef vms X#define VMS 0 X#else X#define VMS 1 X#define DOALARM 1 X#endif X X#ifndef UNIX X#define UNIX 0 X#endif X X#ifndef DOALARM X#define DOALARM 0 X#endif X X#if UNIX X#define IO_ERROR 1 X#endif X X#if VMS X#include X#define IO_ERROR SS$_ABORT X/* X * This creates text files in vanilla RMS on VMS X */ Xextern FILE *fdopen(); X#define CREATE(f, m) fdopen(creat(f, 0, "rat=cr", "rfm=var"), m) X#else X#define CREATE fopen X#endif X X#if DOALARM X#include Xextern int status(); X#endif X X#define MAXKEYS 100 /* Maximum number of keys */ X#define MAXCHARS 0377 /* Maximum number of char's */ X#define UNDEF -1 /* the undefined value */ X Xtypedef struct keyword { X int len; /* Keyword length */ X char last; /* Last byte of keyword */ X char word[1]; /* Keyword text */ X} KEYWORD; X X/* X * Define some frequently used operations as macros: X * hash(p) returns the hash value for this keyword X * used(n) TRUE if this hash value is in use or illegal X * defined(c) TRUE if this character is defined X */ X X#define hash(p) (value[p->word[0]] + p->len + value[p->last]) X#define used(n) ((n > tablesize || n < 0) ? TRUE : mapped[n]) X#define defined(c) (c != UNDEF) X Xchar cval[MAXCHARS]; /* All possible characters */ Xshort cused[MAXCHARS]; /* count of often char used */ Xshort order[MAXKEYS]; /* ordering of key words by subscript */ Xshort neworder[MAXKEYS]; /* the new supposedly improved ordering */ Xshort hashval[MAXKEYS]; /* current hashvalue of the key word */ Xshort value[MAXCHARS]; /* associated value of the character */ Xint mapped[MAXKEYS]; /* track which table entries are in use */ Xchar name[50]; /* bigger than any keyword should be */ X X XKEYWORD *keywds[MAXKEYS]; X Xextern long time(); Xextern char *ctime(); Xextern char *malloc(); Xextern int aredefined(); Xextern char *strchr(); X Xint debug = 0; Xint nkeys = sizeof(keywds) / (sizeof (KEYWORD)); Xint tablesize = sizeof(keywds) / (sizeof (KEYWORD)); Xshort trys = 0; Xint nletters = 0; Xshort kilotrys = 0; Xint atime = 10 * 60; /* default alarm status 10 min. */ Xchar *klimit = NULL; Xchar *textp = NULL; Xlong bigcount = 0; Xshort depth = 0; Xshort k_now = 0; /* value of k for status report */ Xint nosort = FALSE; /* -n sets nosort TRUE */ Xlong start, stop; /* the start and finish times */ Xshort vlimit = 0; Xshort keylimit = 0; Xshort tlimit = 0; Xchar *output = NULL; /* Output file if set */ X Xchar letters[37]; /* string of defined chars */ Xchar *letend = letters; /* -> free space in letters[] */ X Xmain(argc,argv) Xint argc; Xchar *argv[]; X{ X getoptions(argc, argv); X start = time(NULL); X#if DOALARM X signal(SIGALRM,status); X signal(SIGTERM,status); /* status on kill -TERM pid */ X alarm(atime); /* status every N secs */ X#endif X setup(); X dosort(); X printf("The search ordering is\n"); X prntorder(); X if (search(0)) { X#if DOALARM X alarm(0); X#endif X printf("\nSuccess! Associated Char Values Follow:\n"); X prntvals(); X prnthash(); X printf("\n"); X if (output != NULL) X dooutput(); X } X else { X#if DOALARM X alarm(0); X#endif X printf("\nFailed to find char values for hash function\n"); X } X printf("Total search invocations = %ld, max depth = %d\n", X bigcount, depth); X stop = time(NULL); X printf(" Started %s", ctime(&start)); X printf("Finished %s", ctime(&stop)); X} X Xsetup() X{ X register KEYWORD *kp; X register int c; X register int i; X int len; X X for (i = 0; (scanf("%s", name)) != EOF; i++) { X if (i >= MAXKEYS) { X printf("Too many keys, %d max.\n", MAXKEYS); X exit(IO_ERROR); X } X len = strlen(name); X kp = (KEYWORD *) malloc(sizeof (KEYWORD) + len); X if (kp == NULL) { X printf("Out of room allocating keywords\n"); X exit(IO_ERROR); X } X keywds[i] = kp; X kp->len = len; X kp->last = name[len - 1]; X strcpy(&kp->word[0], name); X } X nkeys = (keylimit == 0) ? i : keylimit; X X for (i = 0; i < MAXKEYS; i++) { X hashval[i] = UNDEF; X order[i] = i; X mapped[i] = FALSE; X } X X for (i = 0; i < MAXCHARS; i++) { X cval[i] = 0; X value[i] = UNDEF; X } X X if (!precheck()) { X printf("Perfect hash search terminated\n"); X exit(IO_ERROR); X } X X for (i = 0; i < nkeys; i++) { X c = keywds[i]->word[0]; /* Get first char of keyword */ X cval[c] = c; /* Remember this one used */ X if (cused[c] == 0) /* If it's the first time, */ X ++nletters; /* Count unique letters */ X ++cused[c]; /* count how often letter used */ X c = keywds[i]->last; /* Get last char of keyword */ X cval[c] = c; /* And do the same for it */ X if (cused[c] == 0) X ++nletters; X ++cused[c]; X } X X tablesize = (tlimit == 0 ? MAXKEYS : tlimit); X printf("Perfect hash function finder, CDH Ver. 2.9\n"); X printf("Start time = %s", ctime(&start)); X printf("%d keywords, %d distinct letters.\n", X nkeys, nletters); X nletters = (vlimit > 0) ? vlimit : nletters; X printf("The associated char value limit is %d\n", nletters); X printf("The table size limit is %d\n", tablesize); X X /* X * You should make tablesize at least nkeys + 1 since the X * first value is usually 1 or 2 even if both assoc char X * values are zero since the keyword length is included! X */ X} X Xdosort() X{ X register KEYWORD *kp; X register int i, j; X int k, m; X int newvalues; X X if (nosort) { X printf("No presorting of keywords.\n"); X return; X } X X /* X * first order by sum of frequencies of occurrences of each X * keys 1st and last letter X */ X X for (i = 0; i < nkeys; i++) { X kp = keywds[i]; X order[i] = cused[kp->word[0]] + cused[kp->last]; X } X for (m = 0; m < nkeys; m++) { X for (k = -1, i = 0; i < nkeys; i++) { X if (order[i] > k) { X k = order[i]; X j = i; /* remember keywd subscript */ X } X } X order[j] = 0; X neworder[m] = j; X } X for (i=0; i < nkeys; i++) X order[i] = neworder[i]; X if (debug > 2) { X printf("After first ordering\n"); X prntorder(); X } X X /* X * The second ordering follows, keywds whose values are X * defined by keywds earlier in the order are placed X * immediately after they are defined. This causes hash X * value conflicts to occur as early during the search X * as possible. X */ X X letend = letters; X letters[0] = EOS; X merge(order[0]); /* prime the pump */ X neworder[0] = order[0]; X order[0] = UNDEF; X for (i = 1; i < nkeys;) { X for (newvalues = TRUE; newvalues;) { X newvalues = FALSE; X for (k = 0; k < nkeys; k++) { X if (order[k] == UNDEF) X continue; X if (aredefined(order[k])) { X neworder[i++] = order[k]; X order[k] = UNDEF; X continue; X } X } X for (k = 0; k < nkeys; k++) { X if (order[k] != UNDEF) { X neworder[i++] = order[k]; X merge(order[k]); X order[k] = UNDEF; X newvalues = TRUE; X break; X } X } X } X } X for (i = 0; i < nkeys; i++) X order[i] = neworder[i]; X if (precheck() == FALSE) { X printf("OOPS - call a Guru, the presort botched it\n"); X prntorder(); X exit(IO_ERROR); X } X} X X/* X * merge - adds keywd letters to the string of those defined. X * This could be speeded up, but it's not a critical-path function. X */ X XVOID Xmerge(n) Xint n; X{ X register KEYWORD *kp; X X kp = keywds[n]; X if (debug > 2) X printf("merging in %s\n", kp->word); X if (strchr(letters, kp->word[0]) == NULL) { X *letend++ = kp->word[0]; X *letend = EOS; X } X if (strchr(letters, kp->last) != NULL) { X *letend++ = kp->last; X *letend = EOS; X } X} X X/* X * aredefined - see if 1st & last char of keywd are defined X */ X Xint Xaredefined(n) Xint n; X{ X register KEYWORD *kp; X X kp = keywds[n]; X if (strchr(letters, kp->word[0]) != NULL X && strchr(letters, kp->last) != NULL) X return (TRUE); X else return (FALSE); X} X X/* X * precheck - all keywds length,1st and last char disjoint X */ X Xint Xprecheck() X{ X int pretest; X register KEYWORD *ip, *jp; X short i, j; X short m, k; X char a, b; X X pretest = TRUE; X for (m = 0; m < nkeys; m++) { X i = order[m]; X ip = keywds[i]; X a = ip->word[0]; X b = ip->last; X for (k = m + 1; k < nkeys-1; k++) { X j = order[k]; X jp = keywds[j]; X if (ip->len == jp->len X && ((a == jp->word[0] && b == jp->last) X || (a == jp->last && b == jp->word[0]))) { X pretest = FALSE; X printf("Precheck fails on %s and %s\n", X ip->word, jp->word); X } X } X } X return (pretest); X} X X/* X * prntorder - printout the current order of the keywords X */ X XVOID Xprntorder() X{ X register int i, j; X X for (i = 0, j = 0; i < nkeys; i++) { X if ((j + keywds[order[i]]->len) >= 60) { X printf("\n"); X j = 0; X } X printf(" %s", keywds[order[i]]->word); X j += keywds[order[i]]->len + 1; X } X printf("\n"); X} X X/* X * prntvals - prints out the letter associated values X */ X Xprntvals() X{ X register int i, j; X X for (i = 0, j = 0; i < MAXCHARS; i++) { X if (cval[i]) { X printf("%s %c =%2d,", X ((++j % 10) == 0) ? "\n" : "", X cval[i], value[i]); X } X } X printf("\n"); X} X X/* X * prnthash - prints out the hash values for the keywds X */ X XVOID Xprnthash() X{ X register int i, j; X register KEYWORD *kp; X int swap; X int hmin; X int hmax; X int spread; X X X swap = TRUE; X hmin = MAXKEYS; X hmax = 0; X spread = 0; X for (i = 0; i < nkeys; i++) { X j = hashval[i] = hash(keywds[i]); X hmin = (hmin < j) ? hmin : j; X hmax = (hmax > j) ? hmax : j; X order[i] = i; X } X while (swap) { /* plain vanilla bubble sort */ X swap = FALSE; X for (i = 0; i < nkeys-1; i++) { X if (hashval[order[i+1]] < hashval[order[i]]) { X swap = TRUE; X j = order[i]; X order[i] = order[i+1]; X order[i+1] = j; X } X } X } X printf("Hash min = %d, max = %d, spread = %d\n", X hmin, hmax, hmax - hmin + 1); X for (i=0, j=0; i < nkeys; i++, j++) { X kp = keywds[order[i]]; X if (j + (kp->len + 5) > 60) { X printf("\n"); X j = 0; X } X printf(" %s %d,", kp->word, X hash(keywds[order[i]])); X j += (kp->len + 5); X } X printf("\n"); X} X X/* X * search - calls itself recursively to find char values X */ X Xint Xsearch(k) Xregister int k; X{ X register KEYWORD *p; X register int j; X int m; X short v1, v2, num; X short sub1, sub2, subn; X int thesame; X X thesame = FALSE; X bigcount++; X k_now = k; /* global for status reporting */ X if (k >= nkeys) /* hey - we may be all done */ X return (TRUE); X if (k > depth) /* global for status reporting */ X depth = k; /* keep track of search depth */ X m = order[k]; X p = keywds[m]; X sub1 = p->word[0]; /* sub1 = first letter in word */ X sub2 = p->last; /* sub2 = last letter in word */ X if (sub1 == sub2) X thesame = TRUE; X v1 = value[sub1]; X v2 = value[sub2]; X if (defined(v1) && defined(v2)) { X num = hash(p); /* Both letters defined */ X if (used(num)) X return (FALSE); /* this hash value is in use */ X else { X hashval[m] = num; /* install it */ X mapped[num] = TRUE; X if (search(k + 1)) X return (TRUE); X else { X hashval[m] = UNDEF; X mapped[num] = FALSE; X return (FALSE); X } X } X } X else if (defined(v1)) { X for (j = 0; j <= nletters; j++) { X v2 = j; X num = v1 + p->len + v2; X if (!used(num)) { X hashval[m] = num; X mapped[num] = TRUE; X value[sub2] = v2; X subn = sub2; X if (search(k + 1)) X return (TRUE); X else X remove(m, sub2); X } X } X return (FALSE); X } X else if (defined(v2)) { X for (j = 0; j <= nletters; j++) { X v1 = j; X num = v1 + p->len + v2; X if (!used(num)) { X hashval[m] = num; X mapped[num] =TRUE; X value[sub1] = v1; X subn = sub1; X if (search(k + 1)) X return (TRUE); X else X remove(m, sub1); X } X } X return (FALSE); X } X else { /* neither defined */ X for (j = 0; j <= nletters; j++) { X if (thesame) { X v1 = v2 = j; X num = v1 + p->len + v2; X if (!used(num)) { X hashval[m] = num; X mapped[num] = TRUE; X value[sub1] = v1; /* same as value[sub2] */ X subn = sub1; X if (search(k + 1)) X return (TRUE); X else X remove(m, subn); X } X } X else { X value[sub1] = j; X if (search(k)) /* if never TRUE thru */ X return (TRUE); /* for loop, then FALSE */ X else X value[sub1] = UNDEF; X } X } X return (FALSE); X } X} X X/* X * remove - backup by deleting keywds hash value etc X */ X XVOID Xremove(m, subn) Xregister short m; Xregister short subn; X{ X if (debug > 6) X printf("removing %s, subn = %d\n", keywds[m]->word, subn); X mapped[hashval[m]] = FALSE; X hashval[m] = UNDEF; X value[subn] = UNDEF; X} X X/* X * dooutput writes parser tables to the indicated output file. X */ X Xchar *function[] = { X "", X "int", X "keyword(text)", X "register char\t*text;", X "/*", X " * Look for keyword (string of alpha) in the perfect hash table", X " * Return the index (L_xxx value) or 0 if not found", X " */", X "{", X "\tregister char\t*tp;", X "\tregister int\thash;", X "", X "\tif (*text < FIRST || *text > LAST)", X "\t return (0);", X "\tfor (tp = text; isalpha(*tp); tp++)", X "\t ;", X "\thash = (tp - text);", X "\tif (*--tp < FIRST || *tp > LAST)", X "\t return (0);", X "\thash += (px_assoc - FIRST)[*text] + (px_assoc - FIRST)[*tp];", X "\tif (px_table[hash] == NULL)", X "\t return (0);", X "\tif (strncmp(text, px_table[hash], (tp - text + 1)) != 0)", X "\t return (0);", X "\treturn(hash);", X "}", X "", X NULL, X}; X Xdooutput() X{ X FILE *fd; X register char **funp; X register int i; X int first, last, hval; X X if ((fd = CREATE(output, "w")) == NULL) { X perror(output); X return; X } X fprintf(fd, "#include \n"); X fprintf(fd, "#include \n"); X for (i = 0; i < nkeys; i++) { X fprintf(fd, "#define\tL_%s\t", keywds[order[i]]->word); X if (keywds[order[i]]->len < 14) X putc('\t', fd); X if (keywds[order[i]]->len < 6) X putc('\t', fd); X fprintf(fd, "%d\n", hash(keywds[order[i]])); X } X for (i = MAXCHARS; --i >= 0 && cval[i] == 0;) X ; X last = i; X for (i = 0; i <= last && cval[i] == 0;) X i++; X first = i; X fprintf(fd, "#define FIRST\t'%c'\n", first); X fprintf(fd, "#define LAST\t'%c'\n", last); X fprintf(fd, "static char px_assoc[] = {\n"); X while (i <= last) { X fprintf(fd, "\t%d,\t/* '%c' */\n", value[i], i); X i++; X } X fprintf(fd, "};\n"); X fprintf(fd, "static char *px_table[] = {\n"); X last = 0; X for (i = 0; i < nkeys; i++) { X hval = hash(keywds[order[i]]); X while (last < hval) { X fprintf(fd, "\tNULL,\t\t\t/*%3d\t*/\n", last); X last++; X } X fprintf(fd, "\t\"%s\",\t", keywds[order[i]]->word); X if (keywds[order[i]]->len < 13) X putc('\t', fd); X if (keywds[order[i]]->len < 5) X putc('\t', fd); X fprintf(fd, "/*%3d\t*/\n", hval); X last = hval + 1; X } X fprintf(fd, "};\n"); X for (funp = function; *funp != NULL; funp++) X fprintf(fd, "%s\n", *funp); X fclose(fd); X} X X#if DOALARM X/* X * status - on signal this reports the current statistics X */ X XVOID Xstatus() X{ X fprintf(stderr, X "\nSTATUS: key \"%s\" (%d), search calls = %ld, max depth = %d\n", X keywds[k_now]->word, k_now, bigcount, depth); X fflush(stderr); X signal(SIGTERM,status); X signal(SIGALRM,status); X alarm(atime); X} X#endif X X/* X * G E T O P T I O N S X * X * Generalized command line argument processor. The following X * types of arguments are parsed: X * flags The associated int global is incremented: X * -f f-flag set to 1 X * -f123 f-flag set to 123 (no separator) X * -fg f-flag and g-flag incremented. X * values A value must be present. The associated X * int global receives the value: X * -v123 value set to 123 X * -v 123 value set to 123 X * arguments The associated global (a char *) is X * set to the next argument: X * -f foo argument set to "foo" X */ X X#define FLAG 0 X#define VALUE 1 X#define ARG 2 X#define ERROR 3 X Xtypedef struct argstruct { X char opt; /* Option byte */ X char type; /* FLAG/VALUE/ARG */ X char *name; /* What to set if option seen */ X char *what; /* String for error message */ X} ARGSTRUCT; X Xstatic ARGSTRUCT arginfo[] = { X{ 'd', FLAG, (char *)&debug, "debug" }, X{ 'a', VALUE, (char *)&atime, "alarm time for status" }, X{ 't', VALUE, (char *)&tlimit, "table size limit" }, X{ 'v', VALUE, (char *)&vlimit, "associated value limit" }, X{ 'k', VALUE, (char *)&keylimit, "keyword limit" }, X{ 'n', FLAG, (char *)&nosort, "no sort wanted" }, X{ 'o', ARG, (char *)&output, "parser output file" }, X{ EOS, ERROR, NULL, NULL }, X}; X Xstatic char *argtype[] = { X "flag", "takes value", "takes argument" X}; X Xstatic Xgetoptions(argc, argv) Xint argc; Xchar **argv; X/* X * Process arg's X */ X{ X register char *ap; X register int c; X register ARGSTRUCT *sp; X int i; X int helpneeded; X X getredirection(argc, argv); X helpneeded = FALSE; X for (i = 1; i < argc; i++) { X if ((ap = argv[i]) != NULL && *ap == '-') { X argv[i] = NULL; X for (ap++; (c = *ap++) != EOS;) { X if (isupper(c)) X c = tolower(c); X sp = arginfo; X while (sp->opt != EOS && sp->opt != c) X sp++; X switch (sp->type) { X case FLAG: /* Set the flag */ X if (!isdigit(*ap)) { X (*((int *)sp->name))++; X break; X } X case VALUE: /* -x123 */ X if (isdigit(*ap)) { X *((int *)sp->name) = atoi(ap); X *ap = EOS; X } X else if (*ap == EOS && ++i < argc) { X *((int *)sp->name) = atoi(argv[i]); X argv[i] = NULL; X } X else { X fprintf(stderr, X "Bad option '%c%s' (%s)", X c, ap, sp->what); X fprintf(stderr, ", ignored\n"); X helpneeded++; X } X break; X X case ARG: /* -x foo */ X if (++i < argc) { X *((char **) sp->name) = argv[i]; X argv[i] = NULL; X } X else { X fprintf(stderr, X "Argument needed for '%c' (%s)", X c, sp->what); X fprintf(stderr, ", ignored\n"); X helpneeded++; X } X break; X X case ERROR: X fprintf(stderr, X "Unknown option '%c', ignored\n", c); X helpneeded++; X break; X } X } X } X } X if (helpneeded > 0) { X for (sp = arginfo; sp->opt != EOS; sp++) { X fprintf(stderr, "'%c' -- %s (%s)\n", X sp->opt, sp->what, argtype[sp->type]); X } X } X} X X/* X * getredirection() is intended to aid in porting C programs X * to VMS (Vax-11 C) which does not support '>' and '<' X * I/O redirection. With suitable modification, it may X * useful for other portability problems as well. X */ X X#include X Xgetredirection(argc, argv) Xint argc; Xchar **argv; X/* X * Process vms redirection arg's. Exit if any error is seen. X * If getredirection() processes an argument, argv[i], it is changed X * to NULL. X * X * Warning: do not try to simplify the code for vms. The code X * presupposes that getredirection() is called before any data is X * read from stdin or written to stdout. X * X * Normal usage is as follows: X * X * main(argc, argv) X * int argc; X * char *argv[]; X * { X * register int i; X * int nargs; X * X * getredirection(argc, argv); ** setup redirection X * for (nargs = 0, i = 1; i < argc, i++) { X * if (argv[i] == NULL) ** skip if processed X * continue; ** by getredirection() X * nargs++; ** here is an argument X * ... ** process argv[i] X * } X * if (nargs == 0) { ** no arguments given X * ... X * } X * } X */ X{ X#ifdef vms X register char *ap; /* Argument pointer */ X int i; /* argv[] index */ X int file; /* File_descriptor */ X extern int errno; /* Last vms i/o error */ X X for (i = 1; i < argc; i++) { /* Do all arguments */ X if (*(ap = argv[i]) == '<') { /* ') { /* >file or >>file */ X if (*ap == '>') { /* >>file */ X /* X * If the file exists, and is writable by us, X * call freopen to append to the file (using the X * file's current attributes). Otherwise, create X * a new file with "vanilla" attributes as if X * the argument was given as ">filename". X * access(name, 2) is TRUE if we can write on X * the specified file. X */ X if (access(++ap, 2) == 0) { X if (freopen(ap, "a", stdout) == NULL) { X perror(ap); X exit(errno); X } X else goto erase_arg; X } /* If file accessable */ X else ; /* Else it's just >file */ X } X /* X * On vms, we want to create the file using "standard" X * record attributes. create(...) creates the file X * using the caller's default protection mask and X * "variable length, implied carriage return" X * attributes. dup2() associates the file with stdout. X */ X if ((file = creat(ap, 0, "rat=cr", "rfm=var")) == -1 X || dup2(file, fileno(stdout)) == -1) { X perror(ap); /* Can't create file */ X exit(errno); /* is a fatal error */ X } /* If '>' creation */ Xerase_arg: argv[i] = NULL; /* red. erases argument */ X } /* If redirection */ X } /* For all arguments */ X#endif X#ifdef decus X argc = argv[0]; /* Supress warning msg. */ X#endif X} X X#if UNIX X/* X * The following is missing on some Unix systems X */ X Xchar * Xstrchr(string, c) Xregister char *string; Xregister char c; X/* X * If 'c' is in string, return a pointer to it. X * Else, return NULL. X */ X{ X do { X if (*string == c) X return (string); X } while (*string++ != EOS); X return (NULL); X} X#endif END_OF_FILE if test 29699 -ne `wc -c <'perf.c'`; then echo shar: \"'perf.c'\" unpacked with wrong size! fi # end of 'perf.c' fi echo shar: End of shell archive. exit 0